<?php

declare(strict_types=1);

namespace Core\Basics;

use Zms\HyperfCasts\ArrjoinCasts;
use Zms\HyperfCasts\StrtotimeCasts;
use Zms\Unit\Arr;
use Core\HyperfUnit\Callback;
use Hyperf\Database\ConnectionInterface;
use Hyperf\DbConnection\Db;
use Hyperf\DbConnection\Model\Model;

class BasicsModel extends Model
{
    /**
     * 时间戳格式
     * @var null|string
     */
    public ?string $dateFormat = 'U';

    /**
     * 插入数据,数据有则更新,没有则插入
     * @param array $data
     * @return bool
     */
    public static function conserve(array $data): bool
    {
        $primaryKey = self::primaryKey();
        if (isset($data[$primaryKey])) {
            /**
             * @var static $old
             */
            $old = static::where([$primaryKey => $data[$primaryKey]])->first();
            if ($old) {
                return $old->setAttributes($data)->save();
            }
        }
        return self::getModel()->setAttributes($data)->save();
    }

    /**
     * 获取表主键
     * @return string
     */
    public static function primaryKey(): string
    {
        return Callback::cache(static::class . __METHOD__, function () {
            return static::getModel()->primaryKey;
        });
    }

    /**
     * 批量设置参数
     * @param array $values
     * @return BasicsModel|\Hyperf\Database\Model\Model
     */
    public function setAttributes(array $values): \Hyperf\Database\Model\Model|BasicsModel
    {
        $finds = self::fields();
        foreach ($values as $key => $value) {
            if (in_array($key, $finds)) {//不存在的属性去掉
                $this->setAttribute($key, $value);
            }
        }
        return $this;
    }

    /**
     * 获取数据表所有字段
     */
    public static function fields(): array
    {
        return Callback::cache(static::class . __METHOD__, function () {
            return array_keys(self::attributeLabels());
        });
    }

    /**
     * 获取属性名称
     */
    public static function attributeLabels()
    {
        return Callback::cache(static::class . __METHOD__, function () {
            $table = static::tableName();
            $label = static::connection()->select("select `COLUMN_NAME`,`COLUMN_COMMENT` from INFORMATION_SCHEMA.Columns where table_name='$table'");
            $attributeLabels = [];
            foreach ($label as $item) {
                $attributeLabels[$item->COLUMN_NAME] = $item->COLUMN_COMMENT ?: $item->COLUMN_NAME;
            }
            return $attributeLabels;
        });
    }

    /**
     * 获取数据表名称
     * @return string
     */
    public static function tableName(): string
    {
        return Callback::cache(static::class . __METHOD__, function () {
            return static::getModel()->getTable();
        });
    }

    /**
     * 获取一个连接
     * @return ConnectionInterface
     */
    public static function connection(): ConnectionInterface
    {
        return Db::connection(self::connectionName());
    }

    /**
     * 获取数据库连接名称
     * @return mixed
     */
    public static function connectionName(): mixed
    {
        return Callback::cache(static::class . __METHOD__, function () {
            return static::getModel()->getConnectionName();
        });
    }

    /**
     * 设置属性默认值
     * @param string $key
     * @param mixed $value
     */
    public function setAttribute(string $key, mixed $value): void
    {
        parent::setAttribute($key, $this->setAttributeDefaultValue($key, $value));
    }

    /**
     * 设置属性默认值
     * @param $key
     * @param $value
     * @return mixed
     */
    public function setAttributeDefaultValue($key, $value): mixed
    {
        $casts = $this->casts[$key] ?? '';
        if ($casts && (is_null($value) || (is_string($value) && strlen($value) === 0))) {
            $value = match ($casts) {
                StrtotimeCasts::class, 'int', 'integer', 'real', 'float', 'double', 'boolean', 'bool', 'decimal' => 0,
                ArrjoinCasts::class, 'object', 'array', 'json' => [],
                default => '',
            };
        }
        return $value;
    }

    /**
     * @describe 判断字段是否存在
     * @user zlf <zms-pro@qq.com> 2025/3/11
     * @param string $field
     * @return bool
     */
    public static function hasField(string $field): bool
    {
        return in_array($field, self::fields());
    }

    /**
     * 通过主键获取数据
     * @param array $data 需要处理的数据
     * @param string $join_key 用于关联的字段
     * @param array{label?:string,value?:string,desc?:string} $option
     *              -label  关联数据用于描述的字段
     *              -value  关联数据字段
     *              -desc   新的描述字段名称
     * @return array
     */
    public static function setJoinData(array $data, string $join_key, array $option = []): array
    {
        $label_key = $option['label'] ?? 'name';//对应描述值
        $value_key = $option['value'] ?? self::primaryKey();//对应的关联值
        $desc_find = $option['desc'] ?? "_{$join_key}";//新的字段名称
        if (count($data) === 0) return $data;//如果没有数据直接返回
        //获取关联的数据键
        $values = [];
        foreach ($data as &$row) {
            //不存在此字段直接返回
            if (!isset($row[$join_key])) continue;
            $val = $row[$join_key];
            if (is_array($val)) {
                $values = [...$values, ...$val];
                $row[$desc_find] = [];
            } else {
                $values[] = $val;
                $row[$desc_find] = '';
            }
        }
        //没有有效值,返回
        if (count($values) === 0) return $data;
        $values = Arr::unique($values);

        //获取数据并且转为键值对
        $result = Db::table(self::tableName())->whereIn($value_key, $values)->select([$value_key, $label_key])->limit(count($values))->get()->toArray();
        if (count($result) === 0) return $data;
        $valueMap = Arr::map($result, $value_key, $label_key);

        //把数据塞入主数据中
        foreach ($data as &$row) {
            if (!isset($row[$join_key])) continue;
            $val = $row[$join_key];
            if (is_array($val)) {
                foreach ($val as $v) {
                    $row[$desc_find][] = $valueMap[$v] ?? '';
                }
            } else {
                $row[$desc_find] = $valueMap[$val] ?? '';
            }
        }
        return $data;
    }

    /**
     * 通过获取关联数据
     * @param string|int|array $val
     * @param array{label?:string,value?:string,desc?:string} $option
     *              -label  关联数据用于描述的字段
     *              -value  关联数据字段
     *              -desc   新的描述字段名称
     * @return array|string
     */
    public static function getJoinValue(string|int|array $val, array $option = []): array|string
    {
        $label_key = $option['label'] ?? 'name';//对应描述值
        $value_key = $option['value'] ?? self::primaryKey();//对应的关联值
        $values = is_array($val) ? $val : [$val];
        //获取数据并且转为键值对
        $result = Db::table(self::tableName())->whereIn($value_key, $values)->select([$label_key])->limit(count($values))->get()->toArray();

        $labels = array_column($result, $label_key);
        if (is_array($val)) {
            return $labels;
        }
        return $labels[0] ?? '';
    }

    /**
     * 格式化属性
     * @param array $data
     * @return array
     */
    public static function FormatAttributes(array $data): array
    {
        $model = self::getModel();
        $model->setAttributes($data);
        return $model->getAttributes();
    }

    /**
     * 获取指定属性
     * @param array $keys
     * @return array
     */
    public function getAttrs(array $keys = []): array
    {
        $data = [];
        foreach (self::fields() as $key) {
            if (empty($keys) || in_array($key, $keys)) {
                $data[$key] = $this->getAttribute($key);
            }
        }
        return $data;
    }

    /**
     * 获取当前模型所有属性数据
     * @param bool $showPrimary
     * @return array
     */
    public function getAttrAll(bool $showPrimary = true): array
    {
        $primary = self::primaryKey();
        $finds = self::fields();
        $data = [];
        foreach ($finds as $key) {
            if ($key === $primary && $showPrimary === false) {
                continue;
            }
            $data[$key] = $this->getAttribute($key);
        }
        return $data;
    }


    /**
     * 获取详情
     * @param callable|null $callback
     * @return array
     */
    public function details(?callable $callback = null): array
    {
        if (is_null($callback)) {
            $details = [];
            $labels = self::attributeLabels();
            foreach ($this->toArray() as $key => $value) {
                $details[] = [
                    'label' => $labels[$key] ?? $key, 'type' => 'text', 'value' => $this->getAttribute($key)
                ];
            }
            return $details;
        }
        return $callback($this->toArray(), self::attributeLabels());
    }
}
